package service_token

import (
	"crypto/hmac"
	cRand "crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"math/big"
	"math/rand"
	"strconv"
	"strings"
	"time"
)

// TokenType 终端类型
type TokenType int32

const (
	TokenTypeUnKnown TokenType = 0
	TokenTypePu      TokenType = 1
	TokenTypeUser    TokenType = 2
	TokenTypeUa      TokenType = 3
	TokenTypeService TokenType = 4
)

// TokenInfo token
type TokenInfo struct {
	TokenType  TokenType // 终端类型
	DeviceId   string    // 设备ID
	Userid     string    // 用户账号
	IpId       int64     // 数据库记录ID
	OnlineTime int64
	InletId    string
	RandNum    int64 // 随机数，0：内部随机生成
}

// NewTokenInfo 生成 service token
// routeId: 服务标识, routeId中不能包含字符"+"
func NewTokenInfo(appId, appKey, routeId string) (*TokenInfo, error) {
	if appId == "" || appKey == "" {
		return nil, fmt.Errorf("userid and key can't null")
	}
	if len(routeId) == 0 || strings.Contains(routeId, "+") {
		return nil, fmt.Errorf("error routeId: %v", routeId)
	}

	var (
		timestamp = time.Now().Unix()          // 当前时间戳
		randNum   = randNumber(100000, 999999) // 随机数
	)

	tokenInfo := &TokenInfo{
		TokenType:  TokenTypeService,
		DeviceId:   getServiceTokenPayload(appKey, appId, timestamp, randNum),
		Userid:     appId,
		IpId:       0,
		OnlineTime: timestamp,
		InletId:    routeId,
		RandNum:    randNum,
	}

	return tokenInfo, nil
}

func (c *TokenInfo) String() (string, error) {
	info := c
	if info == nil || info.DeviceId == "" {
		return "", fmt.Errorf("token info can't null")
	}
	if info.RandNum == 0 {
		info.RandNum = randNumber(100000, 999999)
	}

	var params []string
	switch info.TokenType {
	case TokenTypePu:
		params = []string{
			"pu",
			info.DeviceId,
			info.DeviceId,
		}
	case TokenTypeUser:
		params = []string{
			"cu",
			info.DeviceId,
			info.Userid,
		}
	case TokenTypeUa:
		params = []string{
			"ua",
			info.DeviceId,
			info.Userid,
		}
	case TokenTypeService:
		params = []string{
			"sv",
			info.DeviceId,
			info.Userid,
		}
	default:
		return "", fmt.Errorf("token info type error")
	}

	params = append(params,
		strconv.FormatInt(info.IpId, 10),
		strconv.FormatInt(info.RandNum, 10),
		strconv.FormatInt(info.OnlineTime, 10),
		info.InletId,
	)

	key := strings.Join(params, "+")

	return base64.RawURLEncoding.EncodeToString([]byte(key)), nil
}

// getServiceTokenPayload service token 验证token是否有效
func getServiceTokenPayload(appKey string, appId string, timestamp int64, randValue int64) string {
	return HmacSha256(appKey, []byte(fmt.Sprintf("%v+%v+%v", appId, timestamp, randValue)))
}

func HmacSha256(key string, data []byte) string {
	mac := hmac.New(sha256.New, []byte(key))
	mac.Write(data)
	return hex.EncodeToString(mac.Sum(nil))
}

func randNumber(min int64, max int64) int64 {
	a := min
	b := max - min

	n, err := cRand.Int(cRand.Reader, big.NewInt(b))
	if err != nil {
		seed := time.Now().UnixNano()
		return rand.New(rand.NewSource(seed)).Int63n(b) + a
	}

	return n.Int64() + a
}
